package cn.bonoon.kernel.menus;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletContext;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.context.ServletContextAware;

import cn.bonoon.kernel.VisibleScope;
import cn.bonoon.kernel.security.ChannelPrivilege;
import cn.bonoon.kernel.support.IOperator;
import cn.bonoon.kernel.util.MD5Util;
import cn.bonoon.kernel.util.StringHelper;

public final class StandardModuleManager implements ModuleManager, IModuleContext, IModuleInfo, InitializingBean, ServletContextAware {

	private String id;
	private String name;
	/**
	 * 定义一个公共可以访问的路径,如果没有定义,则默认路径为'/'
	 */
	private String endpoint;
	private String defaultEndpoint;
	private DesInfo des;
	/**
	 * 当前验证系统定义的进入系统的路径
	 */
	private String path;

	private ServletContext servletContext;
	private final Map<String, ModuleInfo> modules = new ConcurrentHashMap<String, ModuleInfo>(5);
	private final List<ModuleInfo> sortModules;
	/**
	 * 将会缓存所有读取到的菜单项的定义
	 */
	private final Map<String, ItemInfo> idCached = new HashMap<String, ItemInfo>();
	private final Map<String, ItemInfo> urlCached = new HashMap<String, ItemInfo>();
	/**
	 * 脏数据
	 */
	private final Map<String, List<ItemInfo>> dirtyUrl = new HashMap<String, List<ItemInfo>>(0);
	private final Map<String, TabItem> tabCached = new HashMap<String, TabItem>();
	private final Map<String, TabInfo> tabsCached = new HashMap<String, TabInfo>();
	
	private int menuWidth;
	private int topHeight;
	private int footHeight;
	private String systemLogo;
	private String footName;
	private String systemTitle;
	private String systemName;
	
	/**
	 * 超级管理员的操作菜单，只要是操作管理员就可以操作，没有任何的限制
	 */
	private final ChannelInfo channelSuper = new ChannelInfo(2, "SUPER", "系统管理");
	/**
	 * 系统管理人员的操作菜单，需要根据不同管理人员的操作权限进行分配；使用时需要验证操作权限
	 */
	private final ChannelInfo channelManager = new ChannelInfo(1, "MANAGER", "系统管理");
	/**
	 * 普通个人的个人菜单，需要根据分配的操作权限进行限制
	 */
	private final ChannelInfo channelUser = new ChannelInfo(0, "USER", "个人中心");
	private final Map<String, ItemInfo> itemCached = new HashMap<String, ItemInfo>();
	private final List<VersionInfo> versions = new ArrayList<VersionInfo>();
	private List<PartInfo> parts;
	
	private String baseTabPath;
	
	private String loginName;
	
	private String loginTitle;
	
	private String loginLogo;
	
	private String loginFoot;
	
	private String loginLayout;
	
	private final ILoginInfo loginInfo = new ILoginInfo() {

		@Override
		public String getName() {
			return loginName;
		}

		@Override
		public String getTitle() {
			return loginTitle;
		}

		@Override
		public String getFoot() {
			return loginFoot;
		}

		@Override
		public String getLogo() {
			return loginLogo;
		}

		@Override
		public String getLayout() {
			return loginLayout;
		}
	};
	
	@Override
	public ILoginInfo loginInfo() {
		return loginInfo;
	}
	
	public String getThumbnail() {
		return systemLogo;
	}
	
	public ChannelInfo getChannelManager() {
		return channelManager;
	}
	
	public StandardModuleManager(Collection<ModuleInfo> modules) {
		sortModules = new ArrayList<ModuleInfo>(modules);
		Collections.sort(sortModules);
	}
	
	@Override
	public String tabPath(String id){
		return baseTabPath + id + "/index.do";
	}
	
	public void setParts(List<PartInfo> parts) {
		this.parts = parts;
	}
	
	@Override
	public List<PartInfo> getParts() {
		return parts;
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		String smmid = id;
		if(StringHelper.isEmpty(smmid)){ smmid = "SMMID"; }
		id = MD5Util.getMD5(smmid);

		baseTabPath 	= path("/s/sys/tabs/!");
		path 			= path("/" + path);
		
		if(StringHelper.isNotEmpty(endpoint)){ 
			endpoint = path(endpoint);
		}else{
			endpoint = path;
		}
		
		defaultEndpoint = path("/");
		
		if(StringHelper.isNotEmpty(systemName)){
			systemName = "<img src='" + path(systemName) + "' alt='" + name + "'/>";
		}else{
			systemName = name;
		}
		if(StringHelper.isNotEmpty(systemLogo)){
			systemLogo = path(systemLogo);
		}else{
			systemLogo = null;
		}
		
		if(StringHelper.isEmpty(systemTitle)) systemTitle = name;
		if(menuWidth < MIN_PX) menuWidth = MENU_WIDTH;
		if(topHeight < MIN_PX) topHeight = TOP_HEIGHT;
		if(footHeight < MIN_PX) footHeight = FOOT_HEIGHT;
		
		if(StringHelper.isEmpty(loginName)) loginName = name;
		if(StringHelper.isEmpty(loginTitle)) loginTitle = systemTitle;
		if(StringHelper.isEmpty(loginFoot)) loginFoot = footName;
		if(StringHelper.isEmpty(loginLogo)) loginLogo = systemLogo;
		
		for(ModuleInfo mi : sortModules){
			String oid = mi.getId();
			//把没有被加密的id保存起来
			String md5id = MD5Util.getMD5(oid);
			
			mi.setId(md5id);
			//子系统的进入路径
			mi.setEndpoint(path + "/" + md5id + "!sys.do");
			//子系统功能访问路径的前缀，默认情况下子系统的功能菜单访问路径都应该以ModuleInfo.path开始
			//TODO 这里需要修改，在3.1版本中
			String tp = mi.getPath();
			if(StringHelper.isNotEmpty(tp)){
				mi.setPath(path + "/" + tp);
			}else{
				mi.setPath(path);
			}
			if(StringHelper.isEmpty(mi.getName())){ mi.setName(name); }
			if(StringHelper.isEmpty(mi.getSystemTitle())){ mi.setSystemTitle(systemTitle); }
			if(StringHelper.isEmpty(mi.getSystemLogo())) mi.setSystemLogo(systemLogo);
			if(StringHelper.isEmpty(mi.getFootName())) mi.setFootName(footName);
			if(mi.getFootHeight() < MIN_PX) mi.setFootHeight(footHeight);
			if(mi.getMenuWidth() < MIN_PX) mi.setMenuWidth(menuWidth);
			if(mi.getTopHeight() < MIN_PX) mi.setTopHeight(topHeight);
			if(null == mi.getDes()) mi.setDes(des);
				
			this.modules.put(md5id, mi.visit(oid, this));
		}
		
		//读取各个插件定义的功能
		try {
			Enumeration<URL> uris = getClass().getClassLoader().getResources("cn/bonoon/plugins.properties");
			
			while(uris.hasMoreElements()){
				try(InputStream inStream = uris.nextElement().openStream()){
					Properties ppt = new Properties();
					ppt.load(inStream);
					versions.add(new VersionInfo(servletContext.getContextPath(), ppt));
					//超级管理员的功能
					readMenus(channelSuper, "super.", ppt);
					readConfig(channelSuper, CONFIG_SUPER, ppt);
					//系统管理员的功能
					readMenus(channelManager, "manager.", ppt);
					readConfig(channelManager, CONFIG_MANAGER, ppt);
					//个人管理功能
					readMenus(channelUser, "user.", ppt);
					readConfig(channelUser, CONFIG_USER, ppt);
					
					//read parts
					String part_defs = ppt.getProperty("part.definitions");
					if(StringHelper.isNotEmpty(part_defs)){
						for(String part : part_defs.split(",")){
							if(StringHelper.isEmpty(part)){ continue; }
							String pre = "part." + part, name = ppt.getProperty(pre + ".name");
							if(StringHelper.isEmpty(name)){ continue; }
							String url = ppt.getProperty(pre + ".url");
							if("true".equalsIgnoreCase(ppt.getProperty(pre + ".all"))){
								if(StringHelper.isEmpty(url)){
									url = "/s/sys/{key}!" + part;
								}else if(url.indexOf("{key}") == -1){
									url += "/{key}!" + part;
								}
								for(ModuleInfo mi : sortModules){
									mi.parts.add(__readPart(ppt, pre, name, url));
								}
							}else{
								if(StringHelper.isEmpty(url)){
									url = "/s/sys/" + part;//相对路径
								}
							}
							parts.add(__readPart(ppt, pre, name, url));
						}
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		init(parts, id);
		
		for(ModuleInfo mi : sortModules){
			init(mi.parts, mi.getId());
		}
		
		channelSuper.init(this, this, itemCached, smmid, path + "/sys");
		
		channelManager.init(this, this, itemCached, smmid, path + "/mgr");
		channelUser.setHref(path("/s/user/settings.do"));
		channelUser.init(this, this, itemCached, smmid, path);
	}

	void init(List<PartInfo> parts, String id){
		
		for(PartInfo pi : parts){
			String piurl = pi.getUrl();
			if(piurl.startsWith("/")){
				piurl = path(piurl);
			}
			pi.setUrl(piurl.replace("{key}", id));
		}
		
		Collections.sort(parts);
	}
	private PartInfo __readPart(Properties ppt, String pre, String name, String url) {
		PartInfo pi = new PartInfo(name, url);
		pi.setIconCls(ppt.getProperty(pre + ".iconCls"));
		pi.setLevel(StringHelper.toint(ppt.getProperty(pre + ".level")));
		pi.setOrdinal(__ordinal(ppt, pre));
		pi.setHelper(StringHelper.toboolean(ppt.getProperty(pre + ".helper")));
		return pi;
	}
	
	private void readConfig(ChannelInfo channel, String type, Properties ppt) throws Exception{
		String fns = ppt.getProperty(type + "definitions");
		if(StringHelper.isNotEmpty(fns)){
			String[] tmpFns = fns.split(",");
			if(tmpFns.length > 0){
				TabInfo ti = new TabInfo(__ordinal(ppt, type), type, ppt.getProperty(type + "name"));
				for(String fn : tmpFns){
					if(StringHelper.isEmpty(fn)){ continue; }
					String path = type + fn;
					String clazz = ppt.getProperty(path + ".class");
					if(StringHelper.isEmpty(clazz)){ continue; }
					ClassTabParser ctp = new ClassTabParser(Class.forName(clazz), ppt.getProperty(path + ".scope"));
					TabItem it = new TabItem(__ordinal(ppt, path), path, ppt.getProperty(path + ".name"), ti, ctp);
					ti.add(it);
				}
				
				channel.add(ti);
			}
		}
	}
	
	private void readMenus(ChannelInfo channel, String type, Properties ppt){
		String fns = ppt.getProperty(type + "definitions");
		if(StringHelper.isNotEmpty(fns)){
			for(String fn : fns.split(",")){
				if(StringHelper.isEmpty(fn)){ continue; }
				String path = type + fn;
				String href = ppt.getProperty(path + ".href");
				if(StringHelper.isEmpty(href)){ href = fn; }
				
				MenuInfo sm = new MenuInfo(__ordinal(ppt, path), path, ppt.getProperty(path + ".name"));
				sm.setKey(fn);
				sm.setHref(href);
				sm.setScope(VisibleScope.NULL);
				channel.add(sm);
			}
		}
	}
	
	private int __ordinal(Properties ppt, String name){
		try{
			return Integer.parseInt(ppt.getProperty(name + ".ordinal"));
		}catch(Exception ex){
			return 50;
		}
	}
	
	@Override
	public IModuleInfo module() {
		return this;
	}
	
	@Override
	public String path(String url){
		return StringHelper.path(servletContext.getContextPath(), url);
	}
	
	@Override
	public void add(TabInfo tabs) {
		tabsCached.put(tabs.getId(), tabs);
	}
	
	@Override
	public void add(TabItem tab) {
		tabCached.put(tab.getId(), tab);
	}
	
	@Override
	public void add(ItemInfo item) {
		idCached.put(item.getId(), item);
	}
	
	@Override
	public void add(String href, ItemInfo item) {
		idCached.put(item.getId(), item);
		if(null != href){
			ItemInfo iiDirty = urlCached.put(href, item);
			if (null != iiDirty) {
				// 存在重复的数据
				List<ItemInfo> items = dirtyUrl.get(href);
				if (null == items) {
					items = new ArrayList<ItemInfo>(1);
					dirtyUrl.put(href, items);
				}
				items.add(item);
			}
		}
	}
	
	@Override
	public Collection<TabItem> tabItems() {
		return tabCached.values();
	}
	
	@Override
	public TabItem tabItem(String id) {
		return tabCached.get(id);
	}
	
	@Override
	public TabInfo tab(String id) {
		return tabsCached.get(id);
	}
	
	@Override
	public ItemInfo find(UrlRequest urlRequest) {
		// 尝试用id
		ItemInfo item = idCached.get(urlRequest.getMid());
		if (null != item){
//			if(item.checkNavigationUrl(urlRequest)){
//				/*
//				 * 判断下访问的URL是否在菜单里定义的ID一致。
//				 * 如果不一致，则表示可能是用户通过修改URL的ID参数来访问。
//				 */
//			}
			return item;
		}else{
			// 尝试用url
			String url = urlRequest.getDefinedUrl();
			item = urlCached.get(url);
			if (null != item) {
				urlRequest.resetUrl(item);
				// 如果是直接通过URL访问的，则需要判断该URL被定义菜单项是否有访问的权限。
				return item;
//				if (privilege.hasPrivilege(item)) {
//				}
				/*
				 * 如果当前用户没有访问该路径的权限，则该路径可能被多次进行定义。 所以需要在脏连接里查看，看是否存在多定义的情况
				 */
	//			List<ItemInfo> items = dirtyUrl.get(url);
	//			if (null != items) {
	//				for (ItemInfo it : items) {
	//					if (privilege.hasPrivilege(it)) {
	//						urlRequest.resetUrl(item);
	//						return it;
	//					}
	//				}
	//			}
			}
		}
		// 没有其它方法可以查找了，直接使用访问的路径来处理
		return null;
	}

	@Override
	public String targetUrl(String id) {
		String k = id;
		ModuleInfo mi = modules.get(k);
		if (null != mi){
			k = mi.getEndpoint();
		}
		ItemInfo item = idCached.get(k);
		if(null != item){
			return item.getHref();
		}
		return "";
	}
	
//	@Override
//	public ModuleInfo moduleOrDefault(String key) {
//		if(null != key){
//			ModuleInfo mi = modules.get(key);
//			if(null != mi){
//				return mi;
//			}
//		}
//		if(sortModules.isEmpty()){
//			return sortModules.get(0);
//		}
//		return null;
//	}

	@Override
	public IModuleInfo module(String key) {
		return __module(key);
	}
	
	private IModuleInfo __module(String key){
		ModuleInfo mi = modules.get(key);
		if(null != mi){
			return mi;
		}
		if(key.equals(id)){
			return this;
		}
		return null;
	}
	
	@Override
	public ItemInfo item(String id) {
		return idCached.get(id);
	}

	@Override
	public MenuInfo menu(String menuId) {
		ItemInfo ii = idCached.get(menuId);
		if(null != ii && ii instanceof MenuInfo) { return (MenuInfo)ii; }
		return null;
	}
	
	@Override
	public IStyleInfo getStyle(){
		return new StyleProxy(this);
	}
	
	@Override
	public void setServletContext(ServletContext sc) {
		this.servletContext = sc;
	}
	
	@Override
	public int getMenuWidth() {
		return menuWidth;
	}
	
	public void setMenuWidth(int menuWidth) {
		this.menuWidth = menuWidth;
	}
	
	@Override
	public int getTopHeight() {
		return topHeight;
	}

	public void setTopHeight(int topHeight) {
		this.topHeight = topHeight;
	}
	
	@Override
	public String getSystemLogo() {
		return systemLogo;
	}

	public void setSystemLogo(String systemLogo) {
		this.systemLogo = systemLogo;
	}

	@Override
	public int getFootHeight() {
		return footHeight;
	}
	
	public void setFootHeight(int footHeight) {
		this.footHeight = footHeight;
	}
	
	@Override
	public DesInfo getDes() {
		return des;
	}

	@Override
	public String getFootName() {
		return footName;
	}

	public void setFootName(String footName) {
		this.footName = footName;
	}

	@Override
	public String getSystemName() {
		return systemName;
	}
	
	public void setSystemName(String systemName) {
		this.systemName = systemName;
	}

	@Override
	public String getSystemTitle() {
		return systemTitle;
	}

	public void setSystemTitle(String systemTitle) {
		this.systemTitle = systemTitle;
	}

	@Override
	public Collection<ModuleInfo> modules() {
		return sortModules;
	}

	@Override
	public String getEndpoint() {
		return endpoint;
	}
	
	@Override
	public String getDefaultEndpoint() {
		return defaultEndpoint;
	}

	public void setEndpoint(String endpoint) {
		this.endpoint = endpoint;
	}
	
	@Override
	public final String getId() {
		return id;
	}

	public final void setId(String id) {
		this.id = id;
	}
	
	@Override
	public final String getName() {
		return name;
	}

	public final void setName(String name) {
		this.name = name;
	}

//	@Override
//	public String getHelpDoc() {
//		return helpDoc;
//	}
//
//	public void setHelpDoc(String helpDoc) {
//		this.helpDoc = helpDoc;
//	}
	
	public void setDes(DesInfo des) {
		this.des = des;
	}

	@Override
	public List<VersionInfo> getVersions() {
		return versions;
	}
	
	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}

	@Override
	public List<ChannelInfo> getChannels() {
		return Arrays.asList(channelUser, channelSuper);
	}
	
	@Override
	public List<ChannelInfo> getChannels(IOperator user, ChannelPrivilege privilege) {
		if (user.isSuper()) {
			return Arrays.asList(channelUser, channelSuper);
		}
		if (user.isAdmin() && privilege.hasPrivilege(channelManager)) {
			return Arrays.asList(channelUser, channelManager);
		}
		return Collections.singletonList(channelUser);
	}
	
	@Override
	public ChannelInfo personalCenter() {
		return channelUser;
	}

	public String getLoginName() {
		return loginName;
	}

	public void setLoginName(String loginName) {
		this.loginName = loginName;
	}

	public String getLoginTitle() {
		return loginTitle;
	}

	public void setLoginTitle(String loginTitle) {
		this.loginTitle = loginTitle;
	}

	public String getLoginLogo() {
		return loginLogo;
	}

	public void setLoginLogo(String loginLogo) {
		this.loginLogo = loginLogo;
	}

	public String getLoginFoot() {
		return loginFoot;
	}

	public void setLoginFoot(String loginFoot) {
		this.loginFoot = loginFoot;
	}

	public String getLoginLayout() {
		return loginLayout;
	}

	public void setLoginLayout(String loginLayout) {
		this.loginLayout = loginLayout;
	}

	@Override
	public IShortcutInfo shortcut(String id) {
		IShortcutInfo si = idCached.get(id);
		if(null == si){
			si = tabsCached.get(id);
		}
		return si;
	}
	
	@Override
	public ItemInfo find(String modulekey, String channalid, String... menuid) {
		String mid = modulekey + '-' + channalid;
		if(null != menuid){
			for(String id : menuid){
				mid += '-' + id;
			}
		}
		mid = MD5Util.getMD5(mid);
		return idCached.get(mid);
	}
}
